// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Web;

using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.TeamFoundation.Migration.Toolkit.Wit;
using Microsoft.Teamfoundation.Migration.VersionControl.SharePoint.Lists;
using Microsoft.TeamFoundation.Migration.WorkItemTracking.Wss.Generated;

namespace Microsoft.TeamFoundation.Migration.WorkItemTracking.Wss
{
    /// <summary>
    /// Wss end point (provider) and data source.
    /// </summary>
    [XmlType("InitializationData")]
    public sealed class WssMigrationWorkItemStore : IWorkItemTrackingEndpoint, IMigrationWorkItemStore, IWorkItemUpdatePackageFactory, IDisposable
    {
        private string m_baseUrl;                          //Base Url
        private string m_taskList;                      //Name of the task list
        private QueueConfiguration m_writeQueueConfig;  //Write queue configuration
        private WssHelper m_wssHelper;                  //Helper for Wss functions
        private XmlDocument m_doc;                      //XML document for generating update packages
        private WorkItemTrackingSession m_session;      //Current session object


        #region Properties

        /// <summary>
        /// Base Url for the Wss site.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
        public string BaseUrl
        {
            get { return m_baseUrl; }
            set { m_baseUrl = value; }
        }

        /// <summary>
        /// Display name of the task list for migration.
        /// </summary>
        public string TaskList
        {
            get { return m_taskList; }
            set { m_taskList = value; }
        }

        /// <summary>
        /// Provides access to the Wss helper class.
        /// </summary>
        private WssHelper WssHelper
        {
            get
            {
                if (m_wssHelper == null)
                {
                    m_wssHelper = new WssHelper(m_baseUrl, m_taskList);
                }
                return m_wssHelper;
            }
        }
        #endregion


        #region IWorkItemTrackingEndpoint Members

        /// <summary>
        /// Provides a unique, user-friendly name for this endpoint.
        /// </summary>
        [XmlIgnore]
        public string FullName { get { return StoreName; } }

        /// <summary>
        /// Gets write queue configuration.
        /// </summary>
        [XmlIgnore()]
        public QueueConfiguration WriteQueueConfig
        {
            get { return m_writeQueueConfig; }
            set { m_writeQueueConfig = value; }
        }

        /// <summary>
        /// Returns session the source belongs to.
        /// </summary>
        [XmlIgnore()]
        public WorkItemTrackingSession Session 
        { 
            get { return m_session; } 
            set { m_session = value; }
        }
        #endregion


        #region IMigrationWorkItemStoreFactory Members

        /// <summary>
        /// Creates the work item store for this provider.
        /// </summary>
        /// <returns></returns>
        public IMigrationWorkItemStore CreateWorkItemStore()
        {
            return this;
        }

        #endregion


        #region IMigrationWorkItemStore Members

        /// <summary>
        /// Obtains collection of items scheduled for synchronization.
        /// </summary>
        /// <param name="highWatermark">High watermark object</param>
        /// <returns>Collection of items</returns>
        public IEnumerable<IMigrationWorkItem> GetItems(ref string highWatermark)
        {
            //Build the query
            string queryInnerXml = string.Empty;
            if (string.IsNullOrEmpty(highWatermark) == false)
            {
                //Watermark is a date
                queryInnerXml =
                    "<Where><Geq><FieldRef Name='Modified' /><Value Type='DateTime' IncludeTimeValue='TRUE'>" 
                    + highWatermark + 
                    "</Value></Geq></Where>";
            }
            //Reset the watermark
            highWatermark = WssHelper.GetWssDate(DateTime.Now.AddMinutes(-1d));

            //Get the work items
            return (IEnumerable<IMigrationWorkItem>)GetTasks(queryInnerXml + @"<OrderBy><FieldRef Name=""ID"" /></OrderBy>");
        }

        /// <summary>
        /// Obtains collection of items scheduled for synchronization.
        /// </summary>
        /// <param name="ids">Id of items</param>
        /// <returns>Array of GetWorkItemResult</returns>
        public GetWorkItemResult[] GetItems(string[] ids)
        {
            //Build the query
            StringBuilder sb = new StringBuilder();
            sb.Append("<Where>");
            if (ids.Length > 1)
            {
                sb.Append("<Or>");
            }
            for (int i = 0; i < ids.Length; i++)
            {
                sb.AppendFormat(CultureInfo.InvariantCulture,
                    @"<Eq><FieldRef Name=""ID"" /><Value Type=""Counter"">{0}</Value></Eq>",
                    ids[i]);
            }
            if (ids.Length > 1)
            {
                sb.Append("</Or>");
            }
            sb.Append("</Where>");
            sb.Append(@"<OrderBy><FieldRef Name=""ID"" /></OrderBy>");

            //Get the work items
            List<IMigrationWorkItem> workItems = GetTasks(sb.ToString());

            //Translate to array of GetWorkItemResult
            GetWorkItemResult[] res = new GetWorkItemResult[ids.Length];
            int j = 0;
            for (int i = 0; i < ids.Length; i++)
            {
                if (ids[i] == workItems[j].Watermark.Id)
                {
                    res[i] = new GetWorkItemResult(ids[i], workItems[j]);
                    j++;
                }
                else
                {
                    string msg = string.Format(Resources.Culture, Resources.ErrorWorkItemNotFound, ids[i]);
                    res[i] = new GetWorkItemResult(ids[i], new WssStoreException(msg));
                }
            }
            return res;
        }

        /// <summary>
        /// Submits updates.
        /// </summary>
        /// <param name="updates">Updates to submit</param>
        /// <returns>Update results (one for each update)</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public UpdateResult[] Update(IWorkItemUpdate[] updates)
        {
            UpdateResult[] results = new UpdateResult[updates.Length];
            XmlElement batch = m_doc.CreateElement("Batch");
            batch.SetAttribute("OnError", "Continue");
            Dictionary<string, int> updateMap = new Dictionary<string, int>(StringComparer.InvariantCulture);

            for (int i = 0; i < updates.Length; i++)
            {
                WssWorkItemUpdate update = (WssWorkItemUpdate)updates[i];

                if (update.IsFieldUpdate)
                {
                    string cookie = update.JoinBatch(batch);
                    updateMap.Add(cookie, i);
                }
                else
                {
                    results[i] = update.DoAttachmentOperations();
                }
            }

            if (batch.ChildNodes.Count > 0)
            {
                XmlNode res = WssHelper.ListService.UpdateListItems(WssHelper.ListGuid, batch);

                // Process the results
                Results batchResults = WssHelper.TranslateUpdateResults(res);
                for (int i = 0; i < batchResults.Items.Length; i++)
                {
                    ResultsResult item = batchResults.Items[i];
                    int index = updateMap[item.ID];

                    int errorCode = Convert.ToInt32(item.ErrorCode, 16);
                    if (errorCode != 0)
                    {
                        WssStoreException e = new WssStoreException(item.ErrorText);
                        results[index] = new UpdateResult(e);
                    }
                    else
                    {
                        Watermark wm = new Watermark(item.row.ows_ID, 1);
                        results[index] = new UpdateResult(wm);
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Verifies settings before the synchronization begins.
        /// </summary>
        /// <param name="session">Context</param>
        /// <param name="tfsStore">TFS store from the opposite side</param>
        public void VerifySettings(WorkItemTrackingSession session, WorkItemStore tfsStore)
        {
            // This method intentionally does nothing.
        }

        /// <summary>
        /// Checks whether specified user exists in the store.
        /// </summary>
        /// <param name="name">User name</param>
        /// <returns>True if the user exists</returns>
        public bool IsValidUser(string name)
        {
            return this.WssHelper.IsUserValid(name);
        }

        /// <summary>
        /// Checks whether a field is valid for a work item type.
        /// </summary>
        /// <param name="workItemType">Work item type</param>
        /// <param name="fieldName">Field name</param>
        /// <returns>True if the field is valid for the work item type</returns>
        public bool IsValidField(string workItemType, string fieldName)
        {
            return this.WssHelper.IsFieldValid(fieldName);
        }

        /// <summary>
        /// Synchronizes own metadata with the given project.
        /// </summary>
        /// <param name="project">Project</param>
        public void SynchronizeMetadata(Project project)
        {
            //We don't need to do anything for this implementation.
        }

        /// <summary>
        /// Imports the work item type if needed.
        /// </summary>
        /// <param name="typeName">Work item type name</param>
        /// <param name="tfsTypeName">Work item type name for TFS.</param>
        /// <param name="types">Current work item types.</param>
        public void SynchronizeWorkItemType(string typeName, string tfsTypeName, WorkItemTypeCollection types)
        {
            if (string.IsNullOrEmpty(typeName))
            {
                throw new ArgumentNullException("typeName");
            }
            if (string.IsNullOrEmpty(tfsTypeName))
            {
                throw new ArgumentNullException("tfsTypeName");
            }
            if (types == null)
            {
                throw new ArgumentNullException("types");
            }
            if (types.Contains(tfsTypeName))
            {
                return;
            }

            XmlDocument doc;
            string resName = "Microsoft.TeamFoundation.Migration.WorkItemTracking.Wss." + tfsTypeName + ".xml";
            using (Stream stream = typeof(WssMigrationWorkItemStore).Assembly.GetManifestResourceStream(resName))
            {
                doc = new XmlDocument();
                doc.Load(stream);
            }
            types.Import(doc.DocumentElement);
        }

        /// <summary>
        /// Refreshes store's data.
        /// </summary>
        public void Refresh()
        {
            //Don't need to do anything here.
        }

        /// <summary>
        /// Closes the work item store.
        /// </summary>
        public void Close()
        {
            //Don't need to do anything here.
        }

        /// <summary>
        /// Returns flags describing the work item store.
        /// For Wss, we have attachments, but do not have historical revisions or links.
        /// </summary>
        public MigrationWorkItemData Flags 
        { 
            get { return MigrationWorkItemData.Attachments; } 
        }
        
        /// <summary>
        /// Reopens store from a different thread.
        /// </summary>
        /// <returns>Store safe to work with from the calling thread</returns>
        public IMigrationWorkItemStore Reopen()
        {
            return new WssMigrationWorkItemStore(this);
        }

        /// <summary>
        /// Gets the change list factory.
        /// </summary>
        public IWorkItemUpdatePackageFactory ChangeListFactory
        {
            get { return (IWorkItemUpdatePackageFactory)this; }
        }

        /// <summary>
        /// Gets comparer used with string values.
        /// </summary>
        public StringComparer StringValueComparer
        {
            get { return StringComparer.OrdinalIgnoreCase; }
        }

        /// <summary>
        /// Gets comparer used with field names.
        /// </summary>
        public StringComparer FieldNameComparer
        {
            get { return StringComparer.OrdinalIgnoreCase; }
        }

        /// <summary>
        /// Gets comparer used with work item Ids.
        /// </summary>
        public StringComparer IdComparer
        {
            get { return StringComparer.OrdinalIgnoreCase; }
        }

        /// <summary>
        /// Gets the name of the system.
        /// </summary>
        public string SystemName
        {
            get { return "WSS"; }
        }

        /// <summary>
        /// Gets the name of the data store.  For Wss, this is the base url for the web services.
        /// </summary>
        public string StoreName
        {
            get { return m_baseUrl; }
        }

        /// <summary>
        /// Gets the collection of flat fields for the given work item type.
        /// </summary>
        /// <param name="workItemType">Name of the work item type</param>
        /// <returns>Names of flat fields</returns>
        public IEnumerable<string> GetFlatFields(string workItemType)
        {
            return null;
        }

        #endregion


        #region IWorkItemUpdatePackageFactory Members

        /// <summary>
        /// Creates an update package for the given work item.
        /// </summary>
        /// <param name="source">Source work item</param>
        /// <param name="item">Work item</param>
        /// <returns>Update package object</returns>
        public IWorkItemUpdatePackage CreateUpdatePackage(IMigrationWorkItem source, IMigrationWorkItem item)
        {
            return new WssWorkItemUpdatePackage(m_wssHelper, m_doc, item);
        }

        /// <summary>
        /// Creates an update package for a new work item of the given type.
        /// </summary>
        /// <param name="source">Source work item</param>
        /// <param name="workItemType">Work item type</param>
        /// <returns>Update package object</returns>
        public IWorkItemUpdatePackage CreateUpdatePackage(IMigrationWorkItem source, string workItemType)
        {
            return new WssWorkItemUpdatePackage(m_wssHelper, m_doc, null);
        }

        #endregion

        
        #region Constructors
        
        /// <summary>
        /// Default constructor for the XmlSerializer.
        /// </summary>
        public WssMigrationWorkItemStore()
        {
            //The XmlSerializer requires a parameterless constructor.
            m_doc = new XmlDocument();
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="source">Source store</param>
        private WssMigrationWorkItemStore(WssMigrationWorkItemStore source): this ()
        {
            m_baseUrl = source.m_baseUrl;
            m_taskList = source.m_taskList;
            m_writeQueueConfig = source.WriteQueueConfig;
        }
        #endregion



        #region Private Functions

        /// <summary>
        /// Gets tasks from WSS and translates into a list of IMigrationWorkitem.
        /// </summary>
        /// <param name="queryInnerXml">Xml for the query.</param>
        /// <returns>List of IMigrationWorkitem</returns>
        private List<IMigrationWorkItem> GetTasks(string queryInnerXml)
        {
            List<WssTask> tasks = this.WssHelper.GetTasks(queryInnerXml);
            string tempDir = Session.Policies.AttachmentsConflict.TempDirectory;
            List<IMigrationWorkItem> workItems = new List<IMigrationWorkItem>();

            for (int i = 0; i < tasks.Count; i++)
            {
                workItems.Add(new WssMigrationWorkItem(tasks[i], tempDir));
            }
            return workItems;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes the store.
        /// </summary>
        public void Dispose()
        {
            if (m_wssHelper != null)
            {
                m_wssHelper.Dispose();
            }
        }

        #endregion
    }
}
